GSoC23 — Workweek 15

Introduction

Now that we're approaching a full timing simulation in Icarus Verilog, we somewhat want to be sure that our design "meets" timing. But what does that even mean, to meet timing? It means that all of our latches, flip-flops and other time-critical cells are happy with the signals they get.

A prominent example is the setup and hold time for flip-flops and latches. I have published a blog post where I went into setup and hold times in a bit more detail: GSoC23 — Workweek 5

If we take a look at the SKY130 standard cell library and take for example the cell sky130_fd_sc_hd__dfrtp_1, which is a flip-flop, we can see that it uses several timing checks ($recrem, $setuphold, $width) under the specify section to check various properties of the signals.

If one such timing check is violated during simulation, e.g. the $width timing check detects that a pulse is below a limit, then a warning is printed and additionally a notifier signal is toggled. If the cell is a e.g. flip-flop, the toggling of the notifier signal is detected and in most cell libraries the output of the flip-flop is set to 'x'. Other cells pick up this 'x' value and output an 'x' themselves, and quickly most if not all of the signals in the design become 'x' and the simulation fails. This way, one can immediately see from the simulator output or from the waveforms whether the timing of a design is met or not.

Implementing the $width Timing Check

Since this is the first timing check in Icarus Verilog, I wanted to start with something simple: the $width timing check. Compared to other timing checks, it has only one reference event and no data event (or rather the data event is derived from the reference event with the opposite edge).

It could look like this:

specify
    $width ( posedge CLK &&& cond1 , 1.0:1.0:1.0 , 0 , notifier ) ;
endspecify

In this case, the $width timing check measures the pulse width from posedge to negedge of CLK when cond1 is 1'b1. The triple value 1.0:1.0:1.0 specifies the limit for various process corners, 0 is the threshold (none in this case), and notifier the name of the signal that will be toggled when a violation occurs. If the pulse width is below the limit (and above the threshold), a violation is triggered.

Now how would I implement such a thing in Icarus Verilog with relatively good performance? As a functor of course!

       vvp_fun_tck_width
           ┌───────┐
 ref  ───> │       │ ────> (notifier)
      ───> │       │
      ───> │       │
      ───> │       │
           └───────┘

A functor takes up to 4 inputs and has one output. In this case CLK is fed into the first input and upon a value change at the starting edge the first timestamp is stored. After the arrival of the opposite edge, the second timestamp is stored and the pulse width can be calculated. Is it below the limit (and above the threshold), then the violation message is printed, the current status of the notifier is read and updated. Finally the cbTchkViolation is triggered.

To enable back-annotation via SDF, timing checks must be able to be queried per scope and allow new limits to be set via vpi_put_delays(vpi_tchk, &delays);. This and more has been implemented to make the SDF annotation work.

What does it look live when a violation is triggered? A lot of useful information is printed out:

Timing violation!
    $width( posedge clk:18800 ps, 500 ps : 200 ps, 0 ps, notifier );
    file = vpi_tchk_1.v line = 8
    Scope: tb.my_checker_inst
    Time: 19000 ps
  • First line: The type of timing check, the reference event with timestamp, the expected and actual pulse width, the threshold, the name of the notifier.
  • Second Line: The file and line where the timing check is declared.
  • Third line: The scope of the timing check.
  • Fourth line: The current simulation time.

You can even create your own VPI module that listens for timing check violations via cbTchkViolation. It's simple as that:

#include <vpi_user.h>

PLI_INT32 cb_tchk_violation(struct t_cb_data* cb)
{
    vpi_printf("Hello, tchk!\n");
    if (vpi_get(vpiTchkType, cb->obj) == vpiWidth)
    {
        vpi_printf("It's of type vpiWidth!\n");
    }
    return 0;
}

void tchk_register()
{
    s_cb_data cb_data;
    cb_data.reason = cbTchkViolation;
    cb_data.cb_rtn = &cb_tchk_violation;
    vpi_register_cb(&cb_data);
}

void (*vlog_startup_routines[])() = {
    tchk_register,
    0
};

All of this work has been done in PR #999 and is ready for testing!

There is only one small limitation: Right now you need to insert the record of type .tchk_width manually since automatic insertion of such a record into the .vvp file is not yet implemented. (But definitely planned!)

The record looks like this:

.tchk_width 3 8, "posedge", v0x55a707f6d570_0, v0x000000000000_0, 1.0, 0, v0x55a707f6d450_0;

With the individual elements:

.tchk_width <file_idx> <line_num>, "<starting_edge>", <reference_signal>, <condition>, <limit>, <threshold>, <notifier_signal>;

Hopefully I will get around to it soon and update IVL to automatically insert the records, thereby making the $width timing check usable by all!

The Last Workweek Blog Post

Yes, you read that right, this is the last blog post where I write about the weekly work on my GSoC23 project. This also means that the final project report is coming up soon with a summary of all my work so far - but this won't be the last you hear from me!

Summary

I was able to complete most of the $width timing check in a relatively short time compared to my previous features. This shows that I have become accustomed to Icarus Verilogs codebase during my time at GSoC. It also means that I will be able to add future features with greater efficiency and less effort.

See you at the final project report!